package io.gitee.guoguoleo.chain;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.extra.spring.SpringUtil;
import io.gitee.guoguoleo.annotation.JokerField;
import io.gitee.guoguoleo.annotation.JokerIgnore;
import io.gitee.guoguoleo.enums.FieldStrategy;
import io.gitee.guoguoleo.handler.MetaHandler;
import io.gitee.guoguoleo.model.FieldInfo;
import io.gitee.guoguoleo.model.ObjectMeta;
import io.gitee.guoguoleo.utils.*;
import io.gitee.guoguoleo.wrapper.UpdateWrapper;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.Values;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.update.UpdateSet;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
public class UpdateChain<T> extends Chain<T> {


    private static final long BATCH_SIZE = 1000;//默认修改1000条

    private final Insert insert;
    private final Update update;
    private final Delete delete;


    private Map<String, ObjectMeta> insertHandlerListMap;
    private Map<String, ObjectMeta> updateHandlerListMap;


    public UpdateChain(Class<T> clazz, JdbcTemplate jdbcTemplate) {
        super(clazz, jdbcTemplate);

        String tableName = TableUtil.getTableName(clazz);
        Table table = new Table(tableName);

        this.insert = new Insert();
        this.insert.setTable(table);

        this.update = new Update();
        this.update.setTable(table);

        this.delete = new Delete();
        this.delete.setTable(table);

        this.updateSetList = new CopyOnWriteArrayList<>();

        try {
            //region 运行时执行通用字段
            MetaHandler metaHandler = SpringUtil.getBean(MetaHandler.class);
            metaHandler.setInsertMetaHandlerList(new CopyOnWriteArrayList<>());
            metaHandler.setUpdateMetaHandlerList(new CopyOnWriteArrayList<>());
            metaHandler.startInsertHandler();
            metaHandler.startUpdateHandler();

            if (CollUtil.isNotEmpty(metaHandler.getInsertMetaHandlerList())) {
                this.insertHandlerListMap = metaHandler
                        .getInsertMetaHandlerList()
                        .stream()
                        .collect(
                                Collectors.toMap(ObjectMeta::getFieldName, o -> o)
                        );
            }
            if (CollUtil.isNotEmpty(metaHandler.getUpdateMetaHandlerList())) {
                this.updateHandlerListMap = metaHandler
                        .getUpdateMetaHandlerList()
                        .stream()
                        .collect(
                                Collectors.toMap(ObjectMeta::getFieldName, o -> o)
                        );
            }
            //endregion
        } catch (Exception e) {
            log.info("未实现MetaHandler", e);
        }

    }

    public boolean update(String sql) {
        return JdbcUtil.update(jdbcTemplate, sql);
    }

    @Override
    public boolean update(UpdateWrapper<T> updateWrapper) {
        this.updateSetList = updateWrapper.getUpdate();
        this.whereExpressionList = updateWrapper.getWhere();
        return this.update();
    }

    @Override
    public boolean update(UpdateWrapper<T> updateWrapper, T entity) {
        try {
            Map<String, String> map = BeanUtils.describe(entity);
            for (FieldInfo fieldInfo : this.fieldList.getAllFieldInfoList()) {
                JokerIgnore jokerIgnore = fieldInfo.getField().getAnnotation(JokerIgnore.class);
                if (jokerIgnore != null && !jokerIgnore.ignore() && jokerIgnore.ignoreUpdate()) {
                    continue;
                }
                String value = map.get(fieldInfo.getFieldName());
                if (StringUtils.isNotBlank(value)) {
                    updateWrapper.set(fieldInfo.getFieldName(), value);
                } else {
                    JokerField jokerField = fieldInfo.getField().getAnnotation(JokerField.class);
                    if (jokerField != null && jokerField.updateStrategy().equals(FieldStrategy.ALWAYS)) {
                        updateWrapper.set(fieldInfo.getFieldName(), null);
                    }
                }
            }

            return this.update(updateWrapper);
        } catch (Exception e) {
            log.error("updateWrapper异常", e);
            return false;
        }
    }

    @Override
    public boolean remove(UpdateWrapper<T> updateWrapper) {
        this.updateSetList = updateWrapper.getUpdate();
        this.whereExpressionList = updateWrapper.getWhere();
        return this.remove();
    }

    /**
     * 批量执行update语句
     *
     * @param sql sql语句
     * @return 执行结果
     */
    public boolean batchUpdate(String... sql) {
        return JdbcUtil.batchUpdate(this.jdbcTemplate, sql);
    }

    public boolean save(T entity) {
        try {
            ExpressionList<Column> expressionList = SqlRenderUtil.renderInsertColumn(this.fieldList, entity);
            this.insert.setColumns(expressionList);

            Values values = new Values();
            ParenthesedExpressionList<Expression> expressionParenthesedExpressionList =
                    SqlRenderUtil.renderInsertValueList(expressionList, this.fieldList, entity, this.insertHandlerListMap);
            values.setExpressions(expressionParenthesedExpressionList);

            this.insert.setSelect(values);

            return this.update(this.insert.toString());
        } catch (Exception e) {
            log.error("保存发生异常", e);
            return false;
        }
    }

    /**
     * 批量插入
     *
     * @param entityList 实体类列表
     * @param batchSize  每次执行数量
     * @return 执行结果
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean saveBatch(List<T> entityList, long batchSize) {
        if (CollUtil.isEmpty(entityList)) {
            return false;
        }
        try {
            T entity = entityList.iterator().next();
            ExpressionList<Column> columnExpressionList = SqlRenderUtil.renderInsertColumn(this.fieldList, entity);
            this.insert.setColumns(columnExpressionList);

            List<List<T>> entityListList = ListUtil.poList(entityList, batchSize);
            String[] insertList = new String[entityListList.size()];

            for (int i = 0; i < entityListList.size(); i++) {
                List<T> list = entityListList.get(i);
                Values values = new Values();
                for (T t : list) {
                    ParenthesedExpressionList<Expression> expressionParenthesedExpressionList =
                            SqlRenderUtil.renderInsertValueList(columnExpressionList, this.fieldList, t, this.insertHandlerListMap);
                    values.setExpressions(expressionParenthesedExpressionList);
                }
                this.insert.setSelect(values);
                insertList[i] = this.insert.toString();
            }

            return this.batchUpdate(insertList);
        } catch (Exception e) {
            log.error("保存发生异常", e);
            return false;
        }
    }

    /**
     * 批量插入
     *
     * @param entityList 插入的实体类列表
     * @return 执行结果
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean saveBatch(List<T> entityList) {
        return this.saveBatch(entityList, BATCH_SIZE);
    }

    /**
     * 更新
     *
     * @return 更新结果
     */
    public boolean update() {

        //设置通用字段
        SqlRenderUtil.renderCommonSet(this.updateSetList, this.fieldList, this.updateHandlerListMap);

        if (CollUtil.isEmpty(this.updateSetList)) {
            log.info("没有需要更新的字段");
            return false;
        }

        this.update.setUpdateSets(this.updateSetList);

        this.update.setWhere(SqlRenderUtil.renderCommonWhere(this.fieldList.getDelField()));
        if (CollUtil.isNotEmpty(this.whereExpressionList)) {
            this.update.setWhere(SqlRenderUtil.renderWhere(this.update.getWhere(), this.whereExpressionList));
        }
        return this.update(this.update.toString());
    }

    /**
     * 根据id更新
     *
     * @param entity 实体类
     * @return 更新结果
     */
    public boolean updateById(T entity) {

        try {

            Map<String, String> map = BeanUtils.describe(entity);
            String id = map.get(this.fieldList.getIdField().getFieldName());
            if (StringUtils.isBlank(id)) {
                throw new RuntimeException("主键不能为空");
            }
            this.eq(this.fieldList.getIdField().getFieldName(), map.get(this.fieldList.getIdField().getFieldName()));

            for (FieldInfo fieldInfo : this.fieldList.getAllFieldInfoList()) {
                if (this.fieldList.getIdField().getFieldName().equals(fieldInfo.getFieldName())) {
                    continue;
                }

                JokerIgnore jokerIgnore = fieldInfo.getField().getAnnotation(JokerIgnore.class);
                if (jokerIgnore != null && !jokerIgnore.ignore() && jokerIgnore.ignoreUpdate()) {
                    continue;
                }

                String value = map.get(fieldInfo.getFieldName());
                if (StringUtils.isBlank(value)) {
                    JokerField jokerField = fieldInfo.getField().getAnnotation(JokerField.class);
                    if (jokerField != null && jokerField.updateStrategy().equals(FieldStrategy.ALWAYS)) {
                        this.set(fieldInfo.getFieldName(), null);
                    }

                    continue;
                }

                this.set(fieldInfo.getFieldName(), map.get(fieldInfo.getFieldName()));
            }

            return this.update();
        } catch (Exception e) {
            log.error("保存发生异常", e);
            return false;
        }
    }

    /**
     * 批量更新
     *
     * @param entityList 实体类列表
     * @param batchSize  每次执行数量
     * @return 结果
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean updateBatchById(List<T> entityList, long batchSize) {
        if (CollUtil.isEmpty(entityList)) {
            log.error("没有需要更新的数据");
            return false;
        }
        try {

            List<List<T>> entityListList = ListUtil.poList(entityList, batchSize);


            Column idColumn = new Column(this.fieldList.getIdField().getSqlFieldName());

            List<FieldInfo> fieldInfoList = this.fieldList.getAllFieldInfoList();
            Map<String, FieldInfo> fieldInfoMap = fieldInfoList.stream().collect(Collectors.toMap(FieldInfo::getFieldName, Function.identity()));

            for (List<T> list : entityListList) {
                String[] updateList = new String[list.size()];
                for (int i = 0; i < list.size(); i++) {
                    T t = list.get(i);
                    Update update = new Update();
                    Table table = new Table(TableUtil.getTableName(this.clazz));
                    update.setTable(table);

                    Map<String, String> map = BeanUtils.describe(t);

                    List<UpdateSet> updateSetList = new CopyOnWriteArrayList<>();

                    for (Map.Entry<String, String> entry : map.entrySet()) {

                        //除外
                        if (!fieldInfoMap.containsKey(entry.getKey())) {
                            continue;
                        }

                        if (entry.getKey().equals(this.fieldList.getIdField().getFieldName())) {
                            continue;
                        }

                        FieldInfo fieldInfo = fieldInfoMap.get(entry.getKey());

                        //忽略更新忽略字段
                        JokerIgnore jokerIgnore = fieldInfo.getField().getAnnotation(JokerIgnore.class);
                        if (jokerIgnore != null && !jokerIgnore.ignore() && jokerIgnore.ignoreUpdate()) {
                            continue;
                        }

                        Column column = new Column(FieldUtil.toField(entry.getKey()));
                        String value = entry.getValue();
                        if (StringUtils.isBlank(value)) {
                            JokerField jokerField = fieldInfo.getField().getAnnotation(JokerField.class);
                            if (jokerField != null && jokerField.updateStrategy().equals(FieldStrategy.ALWAYS)) {
                                updateSetList.add(new UpdateSet(column, null));
                            }
                        } else {
                            if (NumberUtil.isNumber(value)) {
                                updateSetList.add(new UpdateSet(column, new LongValue(value)));
                            } else {
                                updateSetList.add(new UpdateSet(column, new StringValue(value)));
                            }
                        }


                    }
                    SqlRenderUtil.renderCommonSet(updateSetList, this.fieldList, this.updateHandlerListMap);

                    update.setUpdateSets(updateSetList);
                    update.setWhere(SqlRenderUtil.renderCommonWhere(this.fieldList.getDelField()));

                    EqualsTo equalsTo = new EqualsTo();
                    equalsTo.withLeftExpression(idColumn);
                    if (NumberUtil.isNumber(map.get(this.fieldList.getIdField().getFieldName()))) {
                        equalsTo.withRightExpression(new LongValue(map.get(this.fieldList.getIdField().getFieldName())));
                    } else {
                        equalsTo.withRightExpression(new StringValue(map.get(this.fieldList.getIdField().getFieldName())));
                    }
                    AndExpression andExpression = new AndExpression(update.getWhere(), equalsTo);
                    update.setWhere(andExpression);

                    updateList[i] = update.toString();
                }
                this.batchUpdate(updateList);
            }

            return true;
        } catch (Exception e) {
            log.error("批量更新发生异常", e);
            return false;
        }
    }

    /**
     * 批量更新
     *
     * @param entityList 实体类列表
     * @return 结果
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean updateBatchById(List<T> entityList) {
        return this.updateBatchById(entityList, BATCH_SIZE);
    }

    public boolean saveOrUpdate(T entity) {

        try {
            Map<String, String> map = BeanUtils.describe(entity);
            String id = map.get(this.fieldList.getIdField().getFieldName());
            if (StringUtils.isBlank(id)) {
                return this.save(entity);
            }
            this.eq(this.fieldList.getIdField().getFieldName(), map.get(this.fieldList.getIdField().getFieldName()));
            T existsEntity = new QueryChain<>(this.clazz, this.jdbcTemplate)
                    .getById(id);
            if (existsEntity == null) {
                return this.save(entity);
            }
            return this.updateById(entity);

        } catch (Exception e) {
            log.error("保存发生异常", e);
            return false;
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public boolean saveOrUpdateBatch(List<T> entityList, long batchSize) {
        if (CollUtil.isEmpty(entityList)) {
            log.error("没有需要修改的数据");
            return false;
        }
        try {
            List<T> saveList = new CopyOnWriteArrayList<>();
            List<T> updateList = new CopyOnWriteArrayList<>();

            List<Serializable> ids = new CopyOnWriteArrayList<>();
            for (T t : entityList) {
                Map<String, String> map = BeanUtils.describe(t);
                if (StringUtils.isNotBlank(map.get(this.fieldList.getIdField().getFieldName()))) {
                    ids.add(map.get(this.fieldList.getIdField().getFieldName()));
                }
            }
            if (CollUtil.isEmpty(ids)) {
                saveList.addAll(entityList);
            } else {
                List<T> existsList = new QueryChain<>(this.clazz, this.jdbcTemplate)
                        .listByIds(ids);
                Map<String, T> existsMap = new ConcurrentHashMap<>();
                for (T t : existsList) {
                    Map<String, String> map = BeanUtils.describe(t);
                    existsMap.put(map.get(this.fieldList.getIdField().getFieldName()), t);
                }

                for (T t : entityList) {
                    Map<String, String> map = BeanUtils.describe(t);
                    String id = map.get(this.fieldList.getIdField().getFieldName());
                    if (StringUtils.isNotBlank(id) && CollUtil.isNotEmpty(existsMap) && existsMap.containsKey(id)) {
                        updateList.add(t);
                    }else {
                        saveList.add(t);
                    }
                }


            }

            if (CollUtil.isNotEmpty(saveList)) {
                this.saveBatch(saveList, batchSize);
            }
            if (CollUtil.isNotEmpty(updateList)) {
                this.updateBatchById(updateList, batchSize);
            }
            return true;
        } catch (Exception e) {
            log.error("保存发生异常", e);
            return false;
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public boolean saveOrUpdateBatch(List<T> entityList) {
        return this.saveOrUpdateBatch(entityList, BATCH_SIZE);
    }

    public boolean remove() {
        if (this.fieldList.getDelField() != null) {
            this.set(this.fieldList.getDelField().getFieldName(), SqlUtil.getDelValue(this.fieldList.getDelField()));
            return this.update();
        } else {
            this.delete.setWhere(SqlRenderUtil.renderCommonWhere(null));
            if (CollUtil.isNotEmpty(this.whereExpressionList)) {
                this.delete.setWhere(SqlRenderUtil.renderWhere(this.delete.getWhere(), this.whereExpressionList));
            }
            return this.update(this.delete.toString());
        }
    }

    public boolean removeById(Serializable id) {
        this.eq(this.fieldList.getIdField().getFieldName(), id);
        return this.remove();
    }

    public boolean removeByIds(Serializable... id) {
        this.in(this.fieldList.getIdField().getFieldName(), id);
        return this.remove();
    }

    public boolean removeByIds(Collection<? extends Serializable> id) {
        this.in(this.fieldList.getIdField().getFieldName(), id);
        return this.remove();
    }


}
